#include "common.h"
#include "error.h"
#include "filesys.h"

static
string GetNameToken(const char*&Rover)
    {
    string  Result;

    while(isalpha(*Rover))
        Result += *Rover++;
    return Result;
    }

static const char* SkipWhite(const char*&Rover)
    {
    while(*Rover && (*Rover == ' ' || *Rover == '\t'))
        ++Rover;
    return Rover;
    }

static const char* SkipToNewLine(const char*&Rover)
    {
    while(*Rover && *Rover != '\n')
        ++Rover;
//    if(*Rover)
//        ++Rover;
    return Rover;
    }
static const char* SkipLine(const char*&Rover)
    {
    SkipToNewLine(Rover);
    if(*Rover)
        ++Rover;
    return Rover;
    }

//LoadErrorFile() - get pointer to char array of file of error messages
static
cache_ptr   LoadErrorFile()
    {
    unique_ptr<IFile>   ErrorFile(FileNew());
    string              Path("$blaccerr.str");

    // if we can't get our error strings, it's instant death
    if(!ErrorFile->OpenRead(Path))
        Exit(-1, "CANT_LOAD_ERROR_STRINGS");

    cache_ptr Result {ErrorFile->Load()};

    return Result;
    }

void TError::GetElement(const char*&Rover)
    {
    bool            StartTag;
    vector<string>  ElementStack;
    string          ElementName;

    assert(*Rover == '<');
    ++Rover;    // skip '<'

    StartTag    = false;
    if(*Rover == '/')
        ++Rover;
    else
        StartTag    = true;
    ElementName    = GetNameToken(Rover);
    if(!ElementName.empty())
        {
        // if it looks like start tag
        if(StartTag)
            {
            enum Com Command=TAGSTART;
            if(*Rover == '/')   // no, it's an empty tag
                ++Rover, Command = TAG;
            if(*Rover == '>')
                {
                ++Rover;
                Commands.push_back(ErrorCommand(Command, ElementName));
                return;
                }
            }
        // else it looked like an end tag
        else if(*Rover == '>')
            {
            ++Rover;
            Commands.push_back(ErrorCommand(TAGEND, ElementName));
            return;
            }
        }
    fprintf(stderr, "'%s': malformed element reference in error string.\n", ElementName.c_str());
    Exit(-1, "ERROR_STRING_BAD_FORMAT");
    }

void TError::ParseErrorString(const char*& Rover)
    {
    string  Part;
    bool    Done    = false;
    vector<string>  ElementStack;

    DUMPVERBOSE("ParseErrorString(%20.20s)\n", Rover);
    ++Rover;
    while(*Rover && !Done)  switch(*Rover)
        {
        case '"' :
            if(*++Rover == '"') // two quotes in a row means one literal quote
                {
                Part.push_back('"');
                continue;
                }
            else                // else, end quote, so emit accumulated literal
                {
                ErrorCommand Command(LITERAL,Part);
                Commands.push_back(Command);
                Done    = true;
                }
            break;
        case '\n' :
            fprintf(stderr, "Unexpected newline in error string.\n");
            Exit(-1, "ERROR_STRING_BAD_FORMAT");
            break;
        case '<' :
            if(!Part.empty())   // flush any partial literal
                {
                Commands.push_back(ErrorCommand(LITERAL, Part));
                Part    = "";
                }
            GetElement(Rover);
            break;
        default:
            Part.push_back(*Rover++);
        }
    // make sure the rest of the line is empty
    SkipWhite(Rover);
    if(*Rover == '#' || *Rover == '\n')
        SkipLine(Rover);
    else if(*Rover == '\0')
        ;
    else
        {
        fprintf(stderr, "'%10.10s': Unexpected text after close quote in error string.\n", Rover);
        Exit(-1, "ERROR_STRING_BAD_FORMAT");
        }
    DUMPVERBOSE("After ParseErrorString: '%20.20s'\n", Rover);
    }
// ParseError() - parse the error message and perform its actions.
void TError::ParseError(const char* File, string Name)
    {
    const char*Rover = File;
    DUMPVERBOSE("ParseError(%s)\n", Name.c_str());
    // search for this error name at start of a line
    while(*Rover && Name.compare(0, string::npos, Rover, Name.length()))
        SkipLine(Rover);
    if(!(Rover && *Rover))
        {
        fprintf(stderr, "Failed to locate error string '%s'.\n", Name.c_str());
        Exit(-1, "ERROR_STRING_BAD_FORMAT");
        }
    SkipLine(Rover);

    // found the error name, parse indented lines that follow
    while(*Rover && strchr(" \t#\n", *Rover))
        {
        string  Token;
        SkipWhite(Rover);   // skip to first non-blank character on line
        if(*Rover == '\n')
            {
            ++Rover;
            continue;
            }
        else if(*Rover == '#')
            {
            SkipLine(Rover);
            continue;
            }
        else if(*Rover == '"')
            ParseErrorString(Rover);
        else if(*Rover == '$')
            {
            string OtherName = GetNameToken(Rover);
            OtherName.erase(0,1);
            ParseError(File, OtherName);
            SkipToNewLine(Rover);
            }
        else if((Token = GetNameToken(Rover)) == "token")
            {
            SkipWhite(Rover);
            Token   = GetNameToken(Rover);
            Commands.push_back(ErrorCommand(TOKEN, Token));
            SkipToNewLine(Rover);
//??? also check that ErrorArg is of type TokenId!
            if(Values.count(Token) == 0)
                {
                fprintf(stderr, "Variable '%s' was not set for error '%s'\n",
                Token.c_str(), Name.c_str());
                Exit(-1, "ERROR_STRING_BAD_FORMAT");
                }
            }
        else if(Token == "show")
            {
            SkipWhite(Rover);
            Token = GetNameToken(Rover);
            Commands.push_back(ErrorCommand(SHOW, Token));
            SkipToNewLine(Rover);
            }
        else
            {
            fprintf(stderr, "'%s': Unknown command in error string.\n", Token.c_str());
            Exit(-1, "ERROR_STRING_BAD_FORMAT");
            }
        while(*Rover && (*Rover == ' ' || *Rover == '\t'))
            ++Rover;
        if(*Rover == '#' || *Rover == '\n')
            SkipLine(Rover);
        else if(*Rover == '\0')
            ;
        else
            {
            fprintf(stderr, "'%.10s': Unexpected extra text on line in error string.\n", Rover);
            Exit(-1, "ERROR_STRING_BAD_FORMAT");
            }
        }
    }

void TError::WriteToFile(unique_ref<TTokens> Tokens, FILE* Output)
    {
    DUMPVERBOSE("TError::WriteToFile Error='%s'\n", Name.c_str());
//    TErrorOutFile*  Foo = new TErrorOutFile(*this);
//    unique_ptr<IErrorOut>   Out(new TErrorOutFile(*this, Output));
    cache_ptr  File {LoadErrorFile()};
    const char* Rover   = File.get();

    ParseError(Rover, Name);
    DUMPVERBOSE("%d commands after ParseError()\n", Commands.size());
    for(auto Command: Commands)switch(Command.Command)
        {
        case SHOW   : printf("Show '%s'\n", Command.Literal.c_str()); break;
        case TAG    : printf("Tag <%s/>\n", Command.Literal.c_str()); break;
        case TAGSTART    : printf("TagStart %s\n", Command.Literal.c_str()); break;
        case TAGEND : printf("TagEnd %s\n", Command.Literal.c_str()); break;
        case LITERAL: printf("Literal '%s'\n", Command.Literal.c_str()); break;
        default : printf("unknown command %d\n", Command.Command);
        }
    for(auto Command: Commands)switch(Command.Command)
        {
        case SHOW       :
            {
            assert(!Command.Literal.empty());
            assert(Values.count(Command.Literal) > 0);
            unique_ref<vector<ErrorArg>> ErrorArgs(Values[Command.Literal]);
            auto  Arg = (*ErrorArgs)[0];
            assert(Arg.TokenId >= 0);

            TToken  Token = Tokens->Get(Arg.TokenId);
            TLineTokens LineTokens  = Tokens->GetLine(Arg.TokenId);
            fprintf(Output, "%s: File '%s', line %d\n",
                Name.c_str(), LineTokens.Name.c_str(), LineTokens.LineNumber);
            // print line
//            TTokenSlice LineSlice = Tokens->Slice(LineTokens.Start, LineTokens.Stop);
//            for(auto Token: LineSlice)
            for(auto Token: Tokens->Slice(LineTokens.Start, LineTokens.Stop))
                fprintf(Output, "%*.*s", Token.TextLen, Token.TextLen, Token.Text);
            fprintf(Output, "\n");
            // print dashes up to token
            for(auto Token: Tokens->Slice(LineTokens.Start, Arg.TokenId))
                for(int i=0; i < Token.TextLen; ++i)
                    fputc('-', Output);
            // print up-arrows underneath token
            for(int i=0; i < Token.TextLen; ++i)
                fputc('^', Output);
            fprintf(Output, "\n");
            }
            break;
        case TAG        :
            {
            assert(!Command.Literal.empty());
            assert(Values.count(Command.Literal) > 0);
            unique_ref<vector<ErrorArg>> ErrorArgs(Values[Command.Literal]);
            for(auto Arg: *ErrorArgs)
                {
                if(Arg.TokenId >= 0)
                    {
                    TToken  Token = Tokens->Get(Arg.TokenId);
#if 0
                    TLineTokens LineTokens  = Tokens->GetLine(Arg.TokenId);
                    for(int iToken=LineTokens.Start; iToken < LineTokens.Stop; ++iToken)
                        {
                        TToken xToken = Tokens->Get(iToken);
                        fprintf(Output, "%*.*s", xToken.TextLen, xToken.TextLen, xToken.Text);
                        }
                    fprintf(Output, "\n");
#endif
                    string Safe = Token.Printable();
                    fprintf(Output, "%s", Safe.c_str());
                    }
                }
            break;
            }
        case TAGSTART   :
            break;
        case TAGEND     :
            break;
        case LITERAL    :
            fprintf(Output, "%s", Command.Literal.c_str());
            break;
        }
    }


void TError::Put(string Name, ErrorArg Arg)
    {
    if(Values.count(Name)==0)
        this->Values[Name]  = unique_ptr<vector<ErrorArg>>(new vector<ErrorArg>);

    unique_ref<vector<ErrorArg>>    Args(this->Values[Name]);

    Args->push_back(Arg);
    }


void ErrorThrow(const char* ErrorName)
    {
    printf("ErrorThrow('%s')\n", ErrorName);
    }

void    Error(unique_ptr<TError> Ptr)
    {
    throw Ptr.release();
    }

void    Error(string ErrorName)
    {
    unique_ptr<TError>  Ptr(new TError(ErrorName));

    Error(std::move(Ptr));
    }

void    Error(string ErrorName,
              string Param1, ErrorArg Arg1)
    {
    unique_ptr<TError>  Ptr(new TError(ErrorName));

    Ptr->Put(Param1, Arg1);
    Error(std::move(Ptr));
    }

void    Error(string ErrorName,
              string Param1, ErrorArg Arg1,
              string Param2, ErrorArg Arg2)
    {
    unique_ptr<TError>  Ptr(new TError(ErrorName));

    Ptr->Put(Param1, Arg1);
    Ptr->Put(Param2, Arg2);
    Error(std::move(Ptr));
    }


void    Error(string ErrorName,
              string Param1, ErrorArg Arg1,
              string Param2, ErrorArg Arg2,
              string Param3, ErrorArg Arg3)
    {
    unique_ptr<TError>  Ptr(new TError(ErrorName));

    Ptr->Put(Param1, Arg1);
    Ptr->Put(Param2, Arg2);
    Ptr->Put(Param3, Arg3);
    Error(std::move(Ptr));
    }
